package com.company.jrlz.service.opcua.config;

import com.company.jrlz.service.UaItemListenerService;
import com.haulmont.cuba.security.app.Authentication;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedSubscription;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.AlwaysRetryPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by ziye on 2023/9/6
 */
public class OpcUaClientTemplate {

    //使用kepserver连接时是2  直连plc是3 PRosys OPCUA是5
    private static final int namespaceIndex = 5;

    private static final AtomicLong clientHandles = new AtomicLong(1L);
    final Lock lock = new ReentrantLock();
    final Condition notificationArrived = lock.newCondition();

    @Autowired
    OpcUaClientFactory opcUaClientFactory;

    @Inject
    private Authentication authentication;

    @Inject
    private UaItemListenerService uaItemListenerService;

    @Inject
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    private Logger log = LoggerFactory.getLogger(OpcUaClientTemplate.class);
    private OpcUaClient opcUaClient;
    private RetryTemplate retryTemplate;
    private long connBackOffPeriod;

    public List<UaMonitoredItem> getItemsAlive() {
        return itemsAlive;
    }

    public void setItemsAlive(List<UaMonitoredItem> itemsAlive) {
        this.itemsAlive = itemsAlive;
    }

    private  List<UaMonitoredItem> itemsAlive= new ArrayList<>();

    // private Subscription defaultSubscription;
    private List<OpcUaClientConnectionListener> connectionListeners = new ArrayList<>();
    private OpcUaProperties properties;

    public OpcUaClientTemplate(OpcUaClientFactory opcUaClientFactory, OpcUaProperties properties) {
        log.debug("OpcUaClientTemplate Load.");
        try {
            opcUaClient = opcUaClientFactory.createUaClient();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("lcy--------OpcUaClientTemplate-----opcUaClient:"+opcUaClient);

        connBackOffPeriod = properties.getRetry().getConnBackOffPeriod();

        retryTemplate = new RetryTemplate();
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        //how many attempts
        simpleRetryPolicy.setMaxAttempts(properties.getRetry().getMaxAttempts());
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        //how much time (in milliseconds) before next attempt
        fixedBackOffPolicy.setBackOffPeriod(properties.getRetry().getBackOffPeriod());
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
        retryTemplate.setRetryPolicy(simpleRetryPolicy);

        this.properties = properties;
    }

    public OpcUaClient getOpcUaClient() {
        return opcUaClient;
    }

    public void setOpcUaClient(OpcUaClient opcUaClient) {
        this.opcUaClient = opcUaClient;
    }

    public RetryTemplate getRetryTemplate() {
        return retryTemplate;
    }

    public void setRetryTemplate(RetryTemplate retryTemplate) {
        this.retryTemplate = retryTemplate;
    }

    public long getConnBackOffPeriod() {
        return connBackOffPeriod;
    }

    public void setConnBackOffPeriod(long connBackOffPeriod) {
        this.connBackOffPeriod = connBackOffPeriod;
    }

    public List<OpcUaClientConnectionListener> getConnectionListeners() {
        return connectionListeners;
    }

    public void setConnectionListeners(List<OpcUaClientConnectionListener> connectionListeners) {
        this.connectionListeners = connectionListeners;
    }

    public OpcUaProperties getProperties() {
        return properties;
    }

    public void setProperties(OpcUaProperties properties) {
        this.properties = properties;
    }

    @Async
    public void connectAlwaysInBackend() {
//        log.debug("OpcUaClientTemplate connectAlwaysInBackend taskExecutor run.");
        RetryTemplate alwaysRetryTemplate = new RetryTemplate();
        alwaysRetryTemplate.setRetryPolicy(new AlwaysRetryPolicy());
        FixedBackOffPolicy connFixedBackOffPolicy = new FixedBackOffPolicy();
        connFixedBackOffPolicy.setBackOffPeriod(connBackOffPeriod);
        alwaysRetryTemplate.setBackOffPolicy(connFixedBackOffPolicy);

        try {
            alwaysRetryTemplate.execute(context -> connect());
        } catch (Exception e) {
            log.warn("==========连接OPC失败："+e.getMessage());
            e.printStackTrace();
        }

    }

    private synchronized boolean connect() throws Exception {

        if (null == opcUaClient) {
            try {
                opcUaClient = opcUaClientFactory.createUaClient();
                fireConnectionListeners();
                return true;
            } catch (Exception e) {
                throw new Exception("Error connecting ua server: ", e);
            }
        } else {
            fireConnectionListeners();
            return true;
        }
    }

    public void addConnectionListener(OpcUaClientConnectionListener connectionListener) {
//        log.info("add opcua connection listener {}", connectionListener);
        this.connectionListeners.add(connectionListener);
    }

    public void fireConnectionListeners() {
//        log.info("fireConnectionListeners, length {}", this.connectionListeners.size());
        List<String> StrList = properties.getCartonQuantitySubscribeNodes();
        List<NodeId> nodes = new ArrayList<>();
        for (String nodeIdStr : StrList) {
            NodeId nodeId = new NodeId(namespaceIndex, nodeIdStr);
            nodes.add(nodeId);
        }
        List<String> strOtherList = properties.getOtherSubscribeNodes();
        List<NodeId> nodeIds = new ArrayList<>();
        if(strOtherList.size() > 0){
            for (String nodeIdStr : strOtherList){
                NodeId nodeId = new NodeId(namespaceIndex, nodeIdStr);
                nodeIds.add(nodeId);
            }
        }
        try {
            createListSubscription(opcUaClient, nodes);
            if(!nodeIds.isEmpty()){
                createListSubscription(opcUaClient, nodeIds);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void onSubscriptionValue(UaMonitoredItem item, DataValue value) {
        authentication.begin();
        //todo 订阅到数据之后的处理
        String node = item.getReadValueId().getNodeId().getIdentifier().toString();
        String getValue = value.getValue().getValue().toString();
        log.info("订阅到[{}],value:[{}]",node,getValue);
        // 监听到变化后的点位，进行对应的业务处理
        CompletableFuture.runAsync(()->
                uaItemListenerService.handlePlcItem(node,getValue)
                );

        authentication.end();
    }

    private void onSubscriptionValue(NodeId nodeId, DataValue value) {
        authentication.begin();
        //todo 订阅到数据之后的处理
        String node = nodeId.getIdentifier().toString();
        String getValue = null;
        if (null != value && null != value.getValue() && null != value.getValue().getValue()) {
            getValue  = value.getValue().getValue().toString();
        }
        // 监听到变化后的点位，进行对应的业务处理
        uaItemListenerService.handlePlcItem(node,getValue);
        authentication.end();
    }

    private void createListSubscription(OpcUaClient client, List<NodeId> nodes) throws Exception {
        /*
         * create a subscription @ 1000ms
         */

        client.connect().get();
        client.getSubscriptionManager().addSubscriptionListener(new SubscriptionListenerImpl(this));

        ManagedSubscription managerSubscription = ManagedSubscription.create(client);
        List<ManagedDataItem> dataItemList = managerSubscription.createDataItems(nodes);

        for (ManagedDataItem managedDataItem : dataItemList){
            managedDataItem.addDataValueListener((t) -> {
                threadPoolTaskExecutor.execute(() ->{
                    log.info(Thread.currentThread().getName()+"  订阅到点位信息 nodeId={},  value={}", managedDataItem.getNodeId().getIdentifier().toString(), t.getValue().getValue());
                    onSubscriptionValue(managedDataItem.getNodeId(), t);
                });
            });
        }
    }


    public boolean writeNodeValue(String nodeId, Variant value) {

        if(null == opcUaClient){
            System.out.println("lcy-------writeNodeValue-----opcUaClient is null");
            return false;
        }

        CompletableFuture<StatusCode> flg = opcUaClient.writeValue(new NodeId(namespaceIndex, nodeId), new DataValue(value, null, null));

        try {
            return flg.get().isGood();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return false;
        }

    }

    public boolean writeNodeValueList(List<String> nodeIds, List<Variant> values){
        AtomicBoolean result = new AtomicBoolean(true);
        List<NodeId> nodeIdList = new ArrayList<>();
        List<DataValue> valueList = new ArrayList<>();;
        nodeIds.forEach(n->{
            nodeIdList.add(new NodeId(namespaceIndex,n));
        });
        values.forEach(v->{
            valueList.add(new DataValue(v, null, null));
        });

        CompletableFuture<List<StatusCode>> listCode = opcUaClient.writeValues(nodeIdList,valueList);
        try {
            listCode.get().forEach(c->{
                result.set(result.get() && c.isGood());
            });
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return result.get();
    }

    public List<DataValue> readNodeListVariant(List<NodeId> nodeIds) throws Exception {
        try {
            CompletableFuture<List<DataValue>>  dataValueCompletableFuture = opcUaClient.readValues(0.0, TimestampsToReturn.Both, nodeIds);
            return dataValueCompletableFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new Exception("Error reading " + nodeIds + " node value: ", e);
        }
    }

    public DataValue readNodeVariant(String nodeId) throws Exception {
        try {
            //  simulation 5    kepServer  远程 3    本地是 2
            CompletableFuture<DataValue> dataValueCompletableFuture = opcUaClient.readValue(10, TimestampsToReturn.Both, new NodeId(namespaceIndex, nodeId));
            return dataValueCompletableFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new Exception("Error reading " + nodeId + " node value: ", e);
        }
    }

    public void close() {
        opcUaClient.disconnect();
    }
}
